#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
  if (argc <= 2) {
    printf("usage: %s ip_address port_number\n", basename(argv[0]));
    return -1;
  }

  const char *ip = argv[1];
  int32_t port = atoi(argv[2]);

  int32_t ret = 0;
  struct sockaddr_in address;
  bzero(&address, sizeof(address));
  address.sin_family = AF_INET;
  inet_pton(AF_INET, ip, &address.sin_addr);
  address.sin_port = htons(port);

  int32_t listen_fd = socket(PF_INET, SOCK_STREAM, 0);
  assert(listen_fd >= 0);
  ret = bind(listen_fd, (struct socketaddr *)&address, sizeof(address));
  assert(ret != -1);
  printf("绑定成功: %d\n", ret);
  ret = listen(listen_fd, 5);
  assert(ret != -1);
  printf("监听成功: %d\n", ret);

  struct sockaddr_in client_address;
  socklen_t client_addrlength = sizeof(client_address);
  int32_t connection_fd = accept(
      listen_fd, (struct socketaddr *)(&client_address), &client_addrlength);
  if (connection_fd < 0) {
    printf("errno is: %d\n", errno);
    close(listen_fd);
  }

  printf("客户端连接成功： %d\n", connection_fd);

  char buf[120];
  fd_set read_fds;
  fd_set exception_fds;
  FD_ZERO(&read_fds);
  FD_ZERO(&exception_fds);

  while (1) {
    memset(buf, 0x0, sizeof(buf));
    /* 每次调用 select 前都要重新在 read_fds 和 exception_fds 中设置文件描述符
     * connection_fd, 因为事件发生之后，文件描述符集合将被内核修改*/
    FD_SET(connection_fd, &read_fds);
    FD_SET(connection_fd, &exception_fds);
    ret = select(connection_fd + 1, &read_fds, NULL, &exception_fds, NULL);
    if (ret < 0) {
      printf("selection failure");
      break;
    }
    if (FD_ISSET(connection_fd, &read_fds)) {
      /* 对于可读事件，采用普通的 recv 函数读取数据 */
      ret = recv(connection_fd, buf, sizeof(buf) - 1, 0);
      if (ret <= 0) {
        break;
      }
      printf("get %d bytes of normal data: %s\n", ret, buf);
    } else if (FD_ISSET(connection_fd, &exception_fds)) {
      /* 对于异常事件，采用带有 MSG_OOB 标志的 recv 函数读取带外数据 */
      ret = recv(connection_fd, buf, sizeof(buf) - 1, MSG_OOB);
      if (ret <= 0) {
        break;
      }
      printf("get %d bytes of oob data: %s\n", ret, buf);
    }
  }
  close(connection_fd);
  close(listen_fd);

  return 0;
}